import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Stack;
import java.util.Vector;

public class PathContainer {
	/***
	 * the symbol of start
	 */
	String start;
	/***
	 * the symbol of end
	 */
	String end;
	/***
	 * paths after clear similar
	 */
	Vector<String> normalPath;
	/***
	 * paths after filtered
	 */
	Vector<String> filterPath;
	/**
	 * refer to all the paths so far
	 */
	Stack<StaticPath> allPaths;
	/**
	 * the path being executing
	 */
	StaticPath current;
	/**
	 * refer to the node index in the path
	 */
	int posY;
	/**
	 * the number of all the paths
	 */
	int count;
	/**
	 * write to the specific file
	 */
	OutputStream f0;
	/***
	 * how many paths have been execited
	 */
	int num;

	Vector<String> printedPath;

	public PathContainer(String start, String end) {
		try {
//			f0 = new FileOutputStream("D:/Projects/Experiment/staticPath.txt");
			f0 = new FileOutputStream(DirectoryUtil.OUTPUT_PATH);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		this.start = start;
		this.end = end;
		filterPath = new Vector<String>();
		normalPath = new Vector<String>();
		printedPath = new Vector<String>();
		allPaths = new Stack<StaticPath>();
		posY = -1;
		count = 0;
	}

	public boolean hasNext() {
		//
		// if (current == null) {
		// System.out
		// .print("curret is null    size:" + allPaths.size() + "\n");
		// } else {
		// System.out.print("current size:" + posY + "/" + current.size()
		// + "  size:" + allPaths.size() + "\n");
		// }

		if (current == null) {
			if (!allPaths.empty()) {
				// not start
				return true;
			} else {
				// nothing...
				return false;
			}
		}

		if (posY == current.size() - 1 && allPaths.empty()) {
			// no more path, close the stream;
			try {
				f0.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			return false;
		}

		return true;
	}

	public StaticNode next() {
		if (current == null || posY == -1 || posY == current.size() - 1) {
			current = allPaths.pop();
			posY = current.getCursor();
		}
		posY++;
		// System.out.println(count + ":(" + posY + "/" + current.size() + ")"
		// + current.getNode(posY).getUnit().getTags().get(0).toString()
		// + "<" + current.getNode(posY).getUnit().toString() + ">");
		return current.getNode(posY);
	}

	@SuppressWarnings("unchecked")
	public void addMethod(StaticMethod method) {
		if (posY == -1) {
			// no path has been interated, just add paths into container.
			for (int i = 0; i < method.size(); i++) {
				if (!this.contains(method.getPath(i))) {
					allPaths.add(method.getPath(i));
				}
			}
		} else {
			if (method.size() == 1) {
				// just one path added to the current path,
				// do not copy the current path
				for (int i = 0; i < method.getPath(0).size(); i++) {
					current.addNode(method.getPath(0).getNode(i), posY + i + 1);
				}
			} else {
				Stack<StaticPath> otherPaths = new Stack<StaticPath>();
				// one path is being interating, insert into the current path.
				List<StaticNode> preNodes = new ArrayList<StaticNode>();
				List<StaticNode> postNodes = new ArrayList<StaticNode>();
				// first cut the current parth into two part
				for (int i = 0; i < current.size(); i++) {
					if (i <= posY) {
						preNodes.add(current.getNode(i));
					} else {
						postNodes.add(current.getNode(i));
					}
				}
				// first cleat the current path
				ArrayList<String> called = (ArrayList<String>) current
						.getCalled();
				// then all them together
				for (int i = 0; i < method.size(); i++) {
					StaticPath path = new StaticPath();
					for (int j = 0; j < preNodes.size(); j++) {
						path.addNode(preNodes.get(j));
					}
					for (int j = 0; j < method.getPath(i).size(); j++) {
						path.addNode(method.getPath(i).getNode(j));
					}
					for (int j = 0; j < postNodes.size(); j++) {
						path.addNode(postNodes.get(j));
					}
					ArrayList<String> clone = (ArrayList<String>) called
							.clone();
					path.setCalled(clone);
					path.setCursor(posY);
					allPaths.add(path);
				}
			}
		}
	}

	private boolean contains(StaticPath path) {
		for (int i = 0; i < allPaths.size(); i++) {
			if (allPaths.get(i).equals(path)) {
				return true;
			}
		}
		return false;
	}

	public StaticPath getPath(int index) {
		return allPaths.get(index);
	}

	public int getPosY() {
		return posY;
	}

	public String toDebugString() {
		StringBuffer sb = new StringBuffer("");
		if (current != null) {
			sb.append(current.toDebugString());
		}
		for (int i = 0; i < allPaths.size(); i++) {
			sb.append(allPaths.get(i).toDebugString());
		}
		return sb.toString();
	}

	@Override
	public String toString() {
		StringBuffer sb = new StringBuffer("");
		if (current != null) {
			sb.append(current.toString());
		}
		for (int i = 0; i < allPaths.size(); i++) {
			sb.append(allPaths.get(i).toString());
		}
		return sb.toString();
	}

	public StaticPath getCurrentPath() {
		return current;
	}

	public int size() {
		return count + 1 + allPaths.size();
	}

	public void removeCurrentNode() {
		current.getNodeS().remove(posY);
		posY--;
	}

	public void finishCurrentPath() {
		for (int i = posY + 1; i < current.size(); i++) {
			current.getNodeS().remove(i);
		}
	}

	public boolean isEndNode() {
		if (posY == current.size() - 1) {
			return true;
		}
		return false;
	}

	public void writeCurrentPath() {
		// System.out.println("write path to file");
		num++;
		clearSimilar();
		clearFilter();
		writeToFile();
		// System.out.println("normalPATH:" + normalPath.size());
		// System.out.println("filterPATH:" + filterPath.size());
		// System.out.println(filterPath.toString());
		normalPath.clear();
		filterPath.clear();
	}

	private void writeToFile() {
		if (num % 2000 == 0) {
			System.out.println(num + " already executed , " + count
					+ " wrote to file , " + allPaths.size()
					+ " left to be executed");
		}
		StringBuffer sb = new StringBuffer("");
		// System.out.println("normal:" + normalPath.size());
		// System.out.println(normalPath.toString());
		// System.out.println("filter:" + filterPath.size());
		// System.out.println(filterPath.toString());
		// System.out.println(printedPath.contains(sb.toString()) + "  "
		// + printedPath.size());
		if (filterPath.size() != 0) {
			for (int i = 0; i < filterPath.size(); i++) {
				sb.append(filterPath.get(i) + "\n");
			}
			sb.append("\n");
			if (!printedPath.contains(sb.toString())) {
				try {
					count++;
					f0.write(sb.toString().getBytes());
					printedPath.add(sb.toString());
					if (count % 2000 == 0) {
						System.out.println("writing " + count + " "
								+ allPaths.size() + " left");
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
			} else {
				// System.out.println("already contains this path "
				// + allPaths.size() + " left");
			}
		} else {
			// System.out.println("filter empty " + allPaths.size() + " left");
		}
	}

	private void clearFilter() {
		boolean findStart = false;
		boolean findEnd = false;
		for (int j = 0; j < normalPath.size(); j++) {
			String line = normalPath.get(j);
			if (!findStart) {
				if (line.equals(start)) {  
					filterPath.add(line);
					findStart = true;
				}
			} else {
				if (line.equals(end)) { 
					filterPath.add(line);
					findEnd = true;
					break;
				} else {
					filterPath.add(line);
				}
			}
			// System.out.print(line + " ");
		}
		// System.out.println("\n"+start + "   " + end);
		// System.out.println(findStart + "   " + findEnd);
		if (!findStart || !findEnd) {
			filterPath.clear();
		}
	}

	private void clearSimilar() {
		Hashtable<String, String> table = new Hashtable<String, String>();
		int index = 1;
		for (int j = 0; j < current.size(); j++) {
			if (!table.contains(current.getNode(j).toString())) {
				table.put(String.valueOf(index), current.getNode(j).toString());
				normalPath.add(current.getNode(j).toString());
				index++;
			}
		}
	}
}
